package org.jetlinks.community.standalone.thingsjs;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.h2.util.StringUtils;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.jetlinks.community.dashboard.DashboardManager;
import org.jetlinks.community.dashboard.MeasurementParameter;
import org.jetlinks.community.dashboard.SimpleMeasurementValue;
import org.jetlinks.community.dashboard.web.request.DashboardMeasurementRequest;
import org.jetlinks.community.dashboard.web.response.DashboardMeasurementResponse;
import org.jetlinks.community.device.entity.DeviceInstanceEntity;
import org.jetlinks.community.device.entity.DeviceProductEntity;
import org.jetlinks.community.device.entity.DeviceProperty;
import org.jetlinks.community.device.service.LocalDeviceInstanceService;
import org.jetlinks.community.device.service.LocalDeviceProductService;
import org.jetlinks.community.elastic.search.service.ElasticSearchService;
import org.jetlinks.community.standalone.entry.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.stream.Collectors;

@RequestMapping("/thingsjs")
@RestController
@Slf4j
public class ThingsJsController {

    @Autowired
    private LocalDeviceProductService productService;

    @Autowired
    private LocalDeviceInstanceService deviceInstanceService;

    @Autowired
    private DashboardManager dashboardManager;

    @Autowired
    private ElasticSearchService searchService;

    @PostMapping
    @Authorize(ignore = true)
    @Operation(summary = "获取相关信息")
    public Mono<ThingsJsResponseMessage<?>> interfaceCommons(@RequestBody(required = false) ThingsJsEntryParam param){
        if (param == null) {

            return productService
                            .createQuery()
                            .where(DeviceProductEntity::getState, 1)
                            .fetch()
                            .map(this:: transformFromDeviceProduct)
                            .collectList()
                            .map(this::toResult);
        } else if (!StringUtils.isNullOrEmpty(param.getClassName())) {
            QueryParamEntity queryParamEntity = new QueryParamEntity();
            queryParamEntity.setPageSize(param.getPageSize() == null? 20 : param.getPageSize());
            queryParamEntity.setPageIndex(param.getPageNum() == null? 1 : param.getPageNum());
            queryParamEntity.setWhere("productName=" + param.getClassName() );
            return Mono.zip(
                        //查询物模型
                        productService.createQuery().where(DeviceProductEntity::getName,param.getClassName()).fetchOne(),
                        //查询出所有设备列表
                        deviceInstanceService.queryPager(queryParamEntity)
                        )
                .flatMap(tp -> {
                    List<String> keys = getKeys(tp.getT1());
                    List<DashboardMeasurementRequest> list = new ArrayList<>();
                    for (DeviceInstanceEntity instance: tp.getT2().getData()) {
                        DashboardMeasurementRequest request = initRequest(instance , keys);
                        list.add(request);
                    }
                    return getDeviceProperty(Flux.fromIterable(list)).collectList().map(this::transformData).map(thingsJsDeviceDtos -> {
                        ThingsJsDeviceResultDto thingsJsDeviceResultDto = new ThingsJsDeviceResultDto();
                        thingsJsDeviceResultDto.setPageNum(tp.getT2().getPageIndex());
                        thingsJsDeviceResultDto.setPageSize(tp.getT2().getPageSize());
                        thingsJsDeviceResultDto.setTotalCount(tp.getT2().getTotal());
                        thingsJsDeviceResultDto.setList(thingsJsDeviceDtos);
                        thingsJsDeviceResultDto.setClassname(tp.getT1().getId());
                        return thingsJsDeviceResultDto;
                    });
                })
                .map(this::toResult);
        } else if (param.getTwinIds() != null  ) {

            if (param.getTwinIds().size() > 0) {
                //查询出所有设备列表
                 return deviceInstanceService
                     .createQuery()
                     .where()
                     .in(DeviceInstanceEntity::getId,param.getTwinIds())
                     .fetch()
                     .flatMap(this::fetchProductInfo)
                     .collectList()
                     .flatMap(request ->
                         getDeviceProperty(Flux.fromIterable(request))
                         .collectList().
                         map(this::transformData))
                    .map(this::toResult);

            } else {

//                QueryParam queryParam = new QueryParam();
//                List<Term> terms = new ArrayList<>();
//                terms.add(new Term().and("deviceId","ws01"));
//                queryParam.setTerms(terms);
//                searchService.query("properties_water-srouce_2023_03", queryParam, SimpleMeasurementValue.class).flatMap(jsonObject -> {
////                    System.out.println(jsonObject.getString("type"));
//                    return Mono.just(jsonObject);
//                });
            }

        } else if (!StringUtils.isNullOrEmpty(param.getLike())) {
            QueryParamEntity queryParamEntity = new QueryParamEntity();
            queryParamEntity.setPageSize(param.getPageSize() == null? 20 : param.getPageSize());
            queryParamEntity.setPageIndex(param.getPageNum() == null? 1 : param.getPageNum());
            queryParamEntity.setWhere("name like %" + param.getLike() + "%");
            //查询出所有设备列表
            return deviceInstanceService
                .queryPager(queryParamEntity)
                .flatMap(pager -> {
                    return Flux.fromIterable(pager.getData())
                        .flatMap(this::fetchProductInfo)
                        .collectList()
                        .flatMap(request -> getDeviceProperty(Flux.fromIterable(request))
                            .collectList().
                            map(this::transformData)).map(thingsJsDeviceDtos -> {
                            ThingsJsDeviceResultDto thingsJsDeviceResultDto = new ThingsJsDeviceResultDto();
                            thingsJsDeviceResultDto.setPageNum(pager.getPageIndex());
                            thingsJsDeviceResultDto.setPageSize(pager.getPageSize());
                            thingsJsDeviceResultDto.setTotalCount(pager.getTotal());
                            thingsJsDeviceResultDto.setList(thingsJsDeviceDtos);
                            return thingsJsDeviceResultDto;
                        });
                }).map(this::toResult);
        }
        return Mono.just(ThingsJsResponseMessage.ok(""));
    }

    /**
     * 请求参数
     * @param deviceInstanceEntity
     * @return
     */
    private Mono<DashboardMeasurementRequest> fetchProductInfo(DeviceInstanceEntity deviceInstanceEntity) {
        return productService.createQuery()
            .where(DeviceProductEntity::getId, deviceInstanceEntity.getProductId())
            .fetchOne().map( productEntity -> {
                List<String> keys = getKeys(productEntity);
                return initRequest(deviceInstanceEntity , keys);
            });
    }



    /**
     * 获取所有的值
     * @param requests
     * @return
     */
    public Flux<DeviceProperty> getDeviceProperty(Flux<DashboardMeasurementRequest> requests) {
        return requests.flatMap(request -> dashboardManager
            .getDashboard(request.getDashboard())
            .flatMap(dash -> dash.getObject(request.getObject()))
            .flatMap(obj -> obj.getMeasurement(request.getMeasurement()))
            .flatMap(meas -> meas.getDimension(request.getDimension()))
            .filter(dim -> !dim.isRealTime()) //实时数据请使用EventSource方式
            .flatMapMany(dim -> dim.getValue(MeasurementParameter.of(request.getParams())))
//            .map(val -> (DeviceProperty)(val.getValue())));

            .map(val -> ((DeviceProperty)((SimpleMeasurementValue)val).getValue())));
    }

    /**
     * 数据转换
     */
    private List<ThingsJsDeviceDto> transformData (List<DeviceProperty> deviceProperties){
        Map< String ,ThingsJsDeviceDto> map = new HashMap();
        for (DeviceProperty devicepProperty : deviceProperties) {
            if (map.containsKey(devicepProperty.getDeviceId())){
                ThingsJsDeviceDto dto = map.get(devicepProperty.getDeviceId());
                List<Map<String , Object>> attrs = dto.getAttrs();
                Map<String , Object> attr = new HashMap<>();
                attr.put("key", devicepProperty.getProperty());
                attr.put("value", devicepProperty.getNumberValue());
                attrs.add(attr);
            } else {
                ThingsJsDeviceDto dto = new ThingsJsDeviceDto();
                dto.setTwinId(devicepProperty.getDeviceId());
                List<Map<String , Object>> attrs = new ArrayList<>();
                Map<String , Object> attr = new HashMap<>();
                attr.put("key", devicepProperty.getProperty());
                attr.put("value", devicepProperty.getNumberValue());
                attrs.add(attr);
                dto.setAttrs(attrs);
                map.put(devicepProperty.getDeviceId() , dto);
            }
        }
        return map.values().stream().collect(Collectors.toList());
    }

    /**
     * POST 方式批量获取仪表数据,不支持获取实时数据.
     *
     * @param requests 请求参数
     * @return 仪表数据
     */
    @PostMapping(value = "/_multi")
    @Authorize(ignore = true)
    public Flux<DashboardMeasurementResponse> getMultiMeasurementValue(@RequestBody Flux<DashboardMeasurementRequest> requests) {
        return requests.flatMap(request -> dashboardManager
            .getDashboard("device")
            .flatMap(dash -> dash.getObject("water-srouce"))
            .flatMap(obj -> obj.getMeasurement("properties"))
            .flatMap(meas -> meas.getDimension(request.getDimension()))
            .filter(dim -> !dim.isRealTime()) //实时数据请使用EventSource方式
            .flatMapMany(dim -> dim.getValue(MeasurementParameter.of(request.getParams())))
            .map(val -> {
                System.out.println("val");
                return DashboardMeasurementResponse.of(request.getGroup(), val);
            }));
    }

    /**
     * 获取当前物模型的所有字段
     * @param productEntity
     * @return
     */
    private List<String> getKeys(DeviceProductEntity productEntity){
        JSONObject model = JSONObject.parseObject(productEntity.getMetadata());
        JSONArray properties = model.getJSONArray("properties");
        return properties.stream().map(propertie->((JSONObject)propertie).getString("id")).collect(Collectors.toList());
    }

    /**
     * 初始化查询对象
     * @param instance
     * @param keys
     * @return
     */
    private DashboardMeasurementRequest initRequest (DeviceInstanceEntity instance , List<String> keys){
        DashboardMeasurementRequest request = new DashboardMeasurementRequest();
        request.setDashboard("device");
        request.setObject(instance.getProductId());
        request.setDimension("history");
        request.setMeasurement("properties");
        Map<String, Object> params = new LinkedHashMap<>();
        params.put("deviceId",instance.getId());
        params.put("history", 1);
        params.put("properties", keys);
        request.setParams(params);
        return request;
    }

    private ThingsJsCategoryDto transformFromDeviceProduct(DeviceProductEntity deviceProduct){
        return ThingsJsCategoryDto.transformFromDeviceProduct(deviceProduct);
    }

    private ThingsJsResponseMessage<?> toResult(Object object) {
        return ThingsJsResponseMessage.ok(object);
    }
}
